技巧74 使用etcd通知容器

Docker镜像被设计成可以在任何地方部署,不过用户经常希望在部署后添加额外信息以便影响运行中的应用程序的行为。此外,运行Docker的机器可能需要保持不变,因此需要一个外部的信息来源(这使得环境变量变得不太适合)。

问题

在运行容器时,需要一个外部的配置源。

解决方案

设置分布式键/值仓库etcd来保存容器配置。

etcd保存着信息片段,并可以作为一个多节点集群的一部分以获得弹性。在本技巧中,你将创建一个etcd集群用于保存配置,并使用一个etcd代理来访问它。

注意

etcd保存的每个值都应该保持小巧——小于512 KB是一个较好的经验规则,超过这个点则应考虑进行基准测试,以确认etcd是否仍然按预期运行。这种限制不是etcd特有的,其他键值存储系统(如Zookeeper和Consul)也应注意这个问题。

因为etcd集群节点需要互相通信,所以第一步是识别外部IP地址。如果节点运行在不同机器上,则需要每一台机器的外部IP,如代码清单9-7所示。

代码清单9-7 识别本地计算机的IP地址

$ ip addr | grep 'inet ' | grep -v 'lo$\|docker0$'
inet 192.168.1.123/24 brd 192.168.1.255 scope global dynamic wlp3s0
inet 172.18.0.1/16 scope global br-0c3386c9db5b

这里我们查找了除回送和Docker之外的所有IPv4网卡。上面这行(第一个IP地址)就是我们所需要的IP,它代表的是在本地网络上的当前机器——如果你对此不是很确信,试试从别的机器ping一下它。

现在我们可以从三节点集群入手,所有节点运行在同一台机器上,如代码清单9-8所示。请谨慎修改每一行的下列参数:公开的及公布的端口,以及集群节点与容器的名称。

代码清单9-8 设置一个三节点etcd集群

$ IMG=quay.io/coreos/etcd:v3.2.7
$ docker pull $IMG
[...]
$ HTTPIP=http://192.168.1.123  ⇽--- 机器的外部IP地址
$ CLUSTER="etcd0=$HTTPIP:2380,etcd1=$HTTPIP:2480,etcd2=$HTTPIP:2580"  ⇽--- 在集群定义中使用机器的外部IP地址,让节点可以相互通信。因为所有的节点都在同一台宿主机上,集群的端口(用于连接其他节点)必须不同
$ ARGS="etcd"
$ ARGS="$ARGS -listen-client-urls http://0.0.0.0:2379"  ⇽--- 用于处理客户端请求的端口
$ ARGS="$ARGS -listen-peer-urls http://0.0.0.0:2380"  ⇽--- 用于与集群其他节点通信的监听端口,与$CLUSTER中指定的端口一致
$ ARGS="$ARGS -initial-cluster-state new"
$ ARGS="$ARGS -initial-cluster $CLUSTER"
$ docker run -d -p 2379:2379 -p 2380:2380 --name etcd0 $IMG \
 $ARGS -name etcd0 -advertise-client-urls $HTTPIP:2379 \
 -initial-advertise-peer-urls $HTTPIP:2380
912390c041f8e9e71cf4cc1e51fba2a02d3cd4857d9ccd90149e21d9a5d3685b
$ docker run -d -p 2479:2379 -p 2480:2380 --name etcd1 $IMG \
 $ARGS -name etcd1 -advertise-client-urls $HTTPIP:2479 \
 -initial-advertise-peer-urls $HTTPIP:2480
446b7584a4ec747e960fe2555a9aaa2b3e2c7870097b5babe65d65cffa175dec
$ docker run -d -p 2579:2379 -p 2580:2380 --name etcd2 $IMG \
 $ARGS -name etcd2 -advertise-client-urls $HTTPIP:2579 \
 -initial-advertise-peer-urls $HTTPIP:2580
3089063b6b2ba0868e0f903a3d5b22e617a240cec22ad080dd1b497ddf4736be
$ curl -L $HTTPIP:2579/version
{"etcdserver":"3.2.7","etcdcluster":"3.2.0"}
$ curl -sSL $HTTPIP:2579/v2/members | python -m json.tool | grep etcd
            "name": "etcd0",  ⇽--- 
            "name": "etcd1",
            "name": "etcd2",  ⇽--- 集群中当前连接的节点

现在集群就已经启动了,并且每个节点都有响应。在上述命令中,指向对等点(peer)的内容是在控制etcd节点如何查找其他节点并与之通信,而指向客户端的内容则定义了其他应用程序如何连接到etcd。

下面用实例来说明一下etcd的分布式特性,如代码清单9-9所示。。

代码清单9-9 测试etcd集群的弹性

$ curl -L $HTTPIP:2579/v2/keys/mykey -XPUT -d value="test key"
{"action":"set","node": >
{"key":"/mykey","value":"test key","modifiedIndex":7,"createdIndex":7}}
$ sleep 5
$ docker kill etcd2
etcd2
$ curl -L $HTTPIP:2579/v2/keys/mykey
curl: (7) couldn't connect to host
$ curl -L $HTTPIP:2379/v2/keys/mykey
{"action":"get","node": >
{"key":"/mykey","value":"test key","modifiedIndex":7,"createdIndex":7}}

上述代码添加了一个键到etcd2节点中,然后杀掉它。不过etcd已经将信息自动复制到其他节点上,因此还是能够提供该信息。尽管上述代码暂停了5 秒,但etcd通常会在1秒内进行复制(即便是在跨不同机器时)。可随时执行 docker start etcd2 让其再次投入使用,所有在此期间做的修改都会复制回去。

可以看出,数据还是可用的,不过必须手工选择另外一个节点进行连接显然有点儿不友好。幸好etcd为此提供了一个解决方案——可以以“代理”模式启动节点,这意味着它不复制任何数据,而只是将请求转发给其他节点,如代码清单9-10所示。

代码清单9-10 使用etcd代理

$ docker run -d -p 8080:8080 --restart always --name etcd-proxy $IMG \
    etcd -proxy on -listen-client-urls http://0.0.0.0:8080 \
    -initial-cluster $CLUSTER
037c3c3dba04826a76c1d4506c922267885edbfa690e3de6188ac6b6380717ef
$ curl -L $HTTPIP:8080/v2/keys/mykey2 -XPUT -d value="t"
{"action":"set","node": >
{"key":"/mykey2","value":"t","modifiedIndex":12,"createdIndex":12}}
$ docker kill etcd1 etcd2
$ curl -L $HTTPIP:8080/v2/keys/mykey2
{"action":"get","node": >
{"key":"/mykey2","value":"t","modifiedIndex":12,"createdIndex":12}}

这样就可以自由地体验当一半的节点离线时etcd是如何工作的了,如代码清单9-11所示。

代码清单9-11 在超过一半的节点离线时使用etcd

$ curl -L $HTTPIP:8080/v2/keys/mykey3 -XPUT -d value="t"
{"errorCode":300,"message":"Raft Internal Error", >
"cause":"etcdserver: request timed out","index":0}
$ docker start etcd2
etcd2
$ curl -L $HTTPIP:8080/v2/keys/mykey3 -XPUT -d value="t"
{"action":"set","node": >
{"key":"/mykey3","value":"t","modifiedIndex":16,"createdIndex":16}}

当一半或更多节点不可用时,etcd将允许读取,而禁止写入。

由此可见,可以在集群的每个节点上启动一个etcd代理作为“大使容器”(ambassador container),用于获取集中式配置,如代码清单9-12所示。

代码清单9-12 在一个大使容器里使用etcd代理

$ docker run -it --rm --link etcd-proxy:etcd ubuntu:14.04.2 bash
root@8df11eaae71e:/# apt-get install -y wget
root@8df11eaae71e:/# wget -q -O- http://etcd:8080/v2/keys/mykey3
{"action":"get","node": >
{"key":"/mykey3","value":"t","modifiedIndex":16,"createdIndex":16}}

提示

大使是Docker用户中流行的一种所谓“Docker模式”。大使容器位于应用程序容器与外部服务之间,负责处理相应请求。它与代理类似,但融入了一些智能用于处理具体情况的需求——就像现实中的大使一样。

一旦在所有环境中运行了etcd,在某个环境中创建机器只需要将其链接到etcd-proxy容器并启动即可——所有该机器的 CD 构建都将使用该环境的正确配置。技巧75将展示如何使用基于etcd的配置来实现零停机时间升级。

讨论

本节展示的大使容器应用了技巧8介绍的link标志。如前所述,在Docker世界链接已经有些失宠,现在实现相同目的的一个更符合习惯的办法是使用技巧80中讲述的虚拟网络上的命名容器。

使用键/值服务器集群来提供一致的世界视图是从管理众多机器上的配置文件向前迈出的一大步,它有助于推动实现技巧70中描述的Docker契约。

results matching ""

    No results matching ""